/*
 * Hibernate OGM, Domain model persistence for NoSQL datastores
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */

package org.hibernate.ogm.utils;

import static org.junit.Assert.fail;

import java.util.Deque;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;

import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import org.jboss.byteman.rule.Rule;
import org.jboss.byteman.rule.helper.Helper;

/**
 * @author Sanne Grinovero (C) 2011 Red Hat Inc.
 * @author Hardy Ferentschik
 */
public class BytemanHelper extends Helper {

	private static final Log log = LoggerFactory.make();

	private static final String DEFAULT_COUNTER = "__DEFAULT_COUNTER_";
	private static final Map<String, AtomicInteger> counters = new ConcurrentHashMap<>();
	private static final Deque<String> concurrentStack = new ConcurrentLinkedDeque<>();

	protected BytemanHelper(Rule rule) {
		super( rule );
	}

	public void sleepASecond() {
		try {
			log.info( "Byteman rule triggered: sleeping a second" );
			Thread.sleep( 1000 );
		}
		catch (InterruptedException e) {
			Thread.currentThread().interrupt();
			log.error( "unexpected interruption", e );
		}
	}

	public void assertBooleanValue(boolean actual, boolean expected) {
		if ( actual != expected ) {
			fail( "Unexpected boolean value" );
		}
	}

	public void countInvocation() {
		countInvocation( DEFAULT_COUNTER );
	}

	public void countInvocation(String counter) {
		log.debug( "Increment call count for " + counter );
		if ( !counters.containsKey( counter ) ) {
			counters.put( counter, new AtomicInteger() );
		}
		counters.get( counter ).incrementAndGet();
	}

	public static int getAndResetInvocationCount() {
		return getAndResetInvocationCount( DEFAULT_COUNTER );
	}

	public static int getAndResetInvocationCount(String counter) {
		if ( !counters.containsKey( counter ) ) {
			return 0;
		}
		return counters.get( counter ).getAndSet( 0 );
	}

	public static void resetCounters() {
		counters.clear();
	}

	/**
	 * Adds a label to a concurrent queue.
	 * Useful to "tag" events to verify they are issued in a specific order,
	 * even though they are generated by different threads.
	 * @param message some label to be recorded
	 */
	public void pushEvent(String message) {
		concurrentStack.add( message );
	}

	/**
	 * Gets the first event label from the concurrent queue,
	 * and removes it.
	 * The next invocation will return the next one from the queue.
	 * @return the first
	 */
	public static String consumeNextRecordedEvent() {
		return concurrentStack.removeFirst();
	}

	public static boolean isEventStackEmpty() {
		return concurrentStack.isEmpty();
	}

	/**
	 * Removes all state from the concurrent queue
	 * used to track events.
	 */
	static void resetEventStack() {
		concurrentStack.clear();
	}
}
